OPC Studio User's Guide and Reference
Installed Examples - Console - ConsoleLiveMapping

Creates an object structure for a boiler, describes its mapping into OPC Data Access server using attributes, and then performs the live mapping. Boiler data is then read, written and/or subscribed to using plain .NET object access.

The main program:

// ConsoleLiveMapping: Creates an object structure for a boiler, describes its mapping into OPC Data Access server using 
// attributes, and then performs the live mapping. Boiler data is then read, written and/or subscribed to using plain .NET 
// object access.
//
// Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html .

using System;
using System.Diagnostics;
using System.Threading;
using OpcLabs.BaseLib.Runtime.InteropServices;
using OpcLabs.EasyOpc.DataAccess;
using OpcLabs.EasyOpc.DataAccess.LiveMapping;
using OpcLabs.EasyOpc.DataAccess.LiveMapping.Extensions;

namespace ConsoleLiveMapping
{
    class Program
    {
        static void Main()
        {
            ComManagement.Instance.AssureSecurityInitialization();

            Console.WriteLine();
            Console.WriteLine("Mapping our data structures to OPC...");
            var mapper = new DAClientMapper();
            var boiler1 = new Boiler();
            mapper.Map(boiler1, new DAMappingContext
                {
                    ServerDescriptor = "OPCLabs.KitServer.2",   // local OPC server
                    // The NodeDescriptor below determines where in the OPC address space we want to map our data to.
                    NodeDescriptor = new DANodeDescriptor { BrowsePath = "/Boilers/Boiler #1"},
                    GroupParameters = 1000,  // requested update rate (for subscriptions)
                });

            Console.WriteLine();
            Console.WriteLine("Reading all data of the boiler...");
            mapper.Read();
            Console.WriteLine($"Drum level is: {boiler1.Drum.LevelIndicator.Output}");

            Console.WriteLine();
            Console.WriteLine("Writing new setpoint value...");
            boiler1.LevelController.SetPoint = 50.0;
            Debug.Assert(!(boiler1.LevelController is null));
            mapper.WriteTarget(boiler1.LevelController, /*recurse:*/false);

            Console.WriteLine();
            Console.WriteLine("Subscribing to boiler data changes...");
            mapper.Subscribe(/*active:*/true);

            Thread.Sleep(30 * 1000);

            Console.WriteLine();
            Console.WriteLine("Unsubscribing from boiler data changes...");
            mapper.Subscribe(/*active:*/false);

            Console.WriteLine();
            Console.WriteLine("Press Enter to continue...");
            Console.ReadLine();
        }
    }
}
' ConsoleLiveMapping: Creates an object structure for a boiler, describes its mapping into OPC OPC Data Access server using 
' attributes, and then performs the live mapping. Boiler data is then read, written and/or subscribed to using plain .NET 
' object access.
'
' Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html .

Imports System.Threading
Imports OpcLabs.BaseLib.Runtime.InteropServices
Imports OpcLabs.EasyOpc.DataAccess
Imports OpcLabs.EasyOpc.DataAccess.LiveMapping
Imports OpcLabs.EasyOpc.DataAccess.LiveMapping.Extensions

' ReSharper disable CheckNamespace
Namespace ConsoleLiveMapping
    ' ReSharper restore CheckNamespace

    Friend Class Program
        <MTAThread> ' needed for COM security initialization to succeed
        Shared Sub Main()
            ComManagement.Instance.AssureSecurityInitialization()

            Console.WriteLine()
            Console.WriteLine("Mapping our data structures to OPC...")
            Dim mapper = New DAClientMapper()
            Dim boiler1 = New Boiler()
            mapper.Map(boiler1, New DAMappingContext With {.ServerDescriptor = "OPCLabs.KitServer.2", .NodeDescriptor = New DANodeDescriptor With {.BrowsePath = "/Boilers/Boiler #1"}, .GroupParameters = 1000}) ' requested update rate (for subscriptions) -  local OPC server
            ' The NodeDescriptor below determines where in the OPC address space we want to map our data to.

            Console.WriteLine()
            Console.WriteLine("Reading all data of the boiler...")
            mapper.Read()
            Console.WriteLine("Drum level is: {0}", boiler1.Drum.LevelIndicator.Output)

            Console.WriteLine()
            Console.WriteLine("Writing new setpoint value...")
            boiler1.LevelController.SetPoint = 50.0
            Debug.Assert(boiler1.LevelController IsNot Nothing)
            mapper.WriteTarget(boiler1.LevelController, False) 'recurse:False

            Console.WriteLine()
            Console.WriteLine("Subscribing to boiler data changes...")
            mapper.Subscribe(True) 'active:True

            Thread.Sleep(30 * 1000)

            Console.WriteLine()
            Console.WriteLine("Unsubscribing from boiler data changes...")
            mapper.Subscribe(False) 'active:True

            Console.WriteLine()
            Console.WriteLine("Press Enter to continue...")
            Console.ReadLine()
        End Sub
    End Class
End Namespace

 

The Boiler class:

//
// Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html .

using System;
using OpcLabs.BaseLib.LiveMapping;
using OpcLabs.EasyOpc.DataAccess;
using OpcLabs.EasyOpc.DataAccess.LiveMapping;

namespace ConsoleLiveMapping
{
    // The Boiler and its constituents are described in our application domain terms, the way we want to work with them.
    // Attributes are used to describe the correspondence between our types and members, and OPC nodes.

    // This is how the boiler looks in OPC address space:
    //  - Boiler #1
    //      - CC1001                    (CustomController)
    //          - ControlOut
    //          - Description
    //          - Input1
    //          - Input2
    //          - Input3
    //      - Drum1001                  (BoilerDrum)
    //          - LIX001                (LevelIndicator)
    //              - Output
    //      - FC1001                    (FlowController)
    //          - ControlOut
    //          - Measurement
    //          - SetPoint
    //      - LC1001                    (LevelController)
    //          - ControlOut
    //          - Measurement
    //          - SetPoint
    //      - Pipe1001                  (BoilerInputPipe)
    //          - FTX001                (FlowTransmitter)
    //              - Output
    //      - Pipe1002                  (BoilerOutputPipe)
    //          - FTX002                (FlowTransmitter)
    //              - Output
    
    [DAType]
    class Boiler
    {
        // Specifying BrowsePath-s here only because we have named the class members differently from OPC node names.

        [DANode(BrowsePath = "Pipe1001")]
        public BoilerInputPipe InputPipe = new BoilerInputPipe();

        [DANode(BrowsePath = "Drum1001")]
        public BoilerDrum Drum = new BoilerDrum();

        [DANode(BrowsePath = "Pipe1002")]
        public BoilerOutputPipe OutputPipe = new BoilerOutputPipe();

        [DANode(BrowsePath = "FC1001")]
        public FlowController FlowController = new FlowController();

        [DANode(BrowsePath = "LC1001")]
        public LevelController LevelController = new LevelController();

        [DANode(BrowsePath = "CC1001")]
        public CustomController CustomController = new CustomController();
    }

    [DAType]
    class BoilerInputPipe
    {
        // Specifying BrowsePath-s here only because we have named the class members differently from OPC node names.

        [DANode(BrowsePath = "FTX001")]
        public FlowTransmitter FlowTransmitter1 = new FlowTransmitter();

        [DANode(BrowsePath = "ValveX001")]
        public Valve Valve = new Valve();
    }

    [DAType]
    class BoilerDrum
    {
        // Specifying BrowsePath-s here only because we have named the class members differently from OPC node names.

        [DANode(BrowsePath = "LIX001")]
        public LevelIndicator LevelIndicator = new LevelIndicator();
    }

    [DAType]
    class BoilerOutputPipe
    {
        // Specifying BrowsePath-s here only because we have named the class members differently from OPC node names.

        [DANode(BrowsePath = "FTX002")]
        public FlowTransmitter FlowTransmitter2 = new FlowTransmitter();
    }

    [DAType]
    class FlowController : GenericController
    {
    }

    [DAType]
    class LevelController : GenericController
    {
    }

    [DAType]
    class CustomController
    {
        [DANode, DAItem]
        public double Input1 { get; set; }

        [DANode, DAItem]
        public double Input2 { get; set; }

        [DANode, DAItem]
        public double Input3 { get; set; }

        [DANode, DAItem(Operations = DAItemMappingOperations.ReadAndSubscribe)] // no OPC writing
        public double ControlOut { get; set; }

        [DANode, DAItem]
        public string Description { get; set; }
    }

    [DAType]
    class FlowTransmitter : GenericSensor
    {
    }

    [DAType]
    class Valve : GenericActuator
    {
    }

    [DAType]
    class LevelIndicator : GenericSensor
    {
    }

    [DAType]
    class GenericController
    {
        [DANode, DAItem(Operations = DAItemMappingOperations.ReadAndSubscribe)] // no OPC writing
        public double Measurement { get; set; }

        [DANode, DAItem]
        public double SetPoint { get; set; }

        [DANode, DAItem(Operations = DAItemMappingOperations.ReadAndSubscribe)] // no OPC writing
        public double ControlOut { get; set; }
    }

    [DAType]
    class GenericSensor
    {
        // Meta-members are filled in by information collected during mapping, and allow access to it later from your code.
        // Alternatively, you can derive your class from DAMappedNode, which will bring in many meta-members automatically.
        [MetaMember("NodeDescriptor")]
        public DANodeDescriptor NodeDescriptor { get; set; }

        [DANode, DAItem(Operations = DAItemMappingOperations.ReadAndSubscribe)] // no OPC writing
        public double Output
        {
            get => _output;
            set
            {
                _output = value;
                Console.WriteLine($"Sensor \"{NodeDescriptor}\" output is now {value}.");
            }
        }

        private double _output;
    }

    [DAType]
    class GenericActuator
    {
        [DANode, DAItem]
        public double Input { get; set; }
    }
}
'
' Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html .

Imports OpcLabs.BaseLib.LiveMapping
Imports OpcLabs.EasyOpc.DataAccess
Imports OpcLabs.EasyOpc.DataAccess.LiveMapping

' ReSharper disable CheckNamespace
Namespace ConsoleLiveMapping
    ' ReSharper restore CheckNamespace

    ' The Boiler and its constituents are described in our application domain terms, the way we want to work with them.
    ' Attributes are used to describe the correspondence between our types and members, and OPC nodes.

    ' This is how the boiler looks in OPC address space:
    '  - Boiler #1
    '      - CC1001                    (CustomController)
    '          - ControlOut
    '          - Description
    '          - Input1
    '          - Input2
    '          - Input3
    '      - Drum1001                  (BoilerDrum)
    '          - LIX001                (LevelIndicator)
    '              - Output
    '      - FC1001                    (FlowController)
    '          - ControlOut
    '          - Measurement
    '          - SetPoint
    '      - LC1001                    (LevelController)
    '          - ControlOut
    '          - Measurement
    '          - SetPoint
    '      - Pipe1001                  (BoilerInputPipe)
    '          - FTX001                (FlowTransmitter)
    '              - Output
    '      - Pipe1002                  (BoilerOutputPipe)
    '          - FTX002                (FlowTransmitter)
    '              - Output

    <DAType()> _
    Friend Class Boiler
        ' Specifying BrowsePath-s here only because we have named the class members differently from OPC node names.

        <DANode(BrowsePath:="Pipe1001")> _
        Public InputPipe As New BoilerInputPipe()

        <DANode(BrowsePath:="Drum1001")> _
        Public Drum As New BoilerDrum()

        <DANode(BrowsePath:="Pipe1002")> _
        Public OutputPipe As New BoilerOutputPipe()

        <DANode(BrowsePath:="FC1001")> _
        Public FlowController As New FlowController()

        <DANode(BrowsePath:="LC1001")> _
        Public LevelController As New LevelController()

        <DANode(BrowsePath:="CC1001")> _
        Public CustomController As New CustomController()
    End Class

    <DAType()> _
    Friend Class BoilerInputPipe
        ' Specifying BrowsePath-s here only because we have named the class members differently from OPC node names.

        <DANode(BrowsePath:="FTX001")> _
        Public FlowTransmitter1 As New FlowTransmitter()

        <DANode(BrowsePath:="ValveX001")> _
        Public Valve As New Valve()
    End Class

    <DAType()> _
    Friend Class BoilerDrum
        ' Specifying BrowsePath-s here only because we have named the class members differently from OPC node names.

        <DANode(BrowsePath:="LIX001")> _
        Public LevelIndicator As New LevelIndicator()
    End Class

    <DAType()> _
    Friend Class BoilerOutputPipe
        ' Specifying BrowsePath-s here only because we have named the class members differently from OPC node names.

        <DANode(BrowsePath:="FTX002")> _
        Public FlowTransmitter2 As New FlowTransmitter()
    End Class

    <DAType()> _
    Friend Class FlowController
        Inherits GenericController

    End Class

    <DAType()> _
    Friend Class LevelController
        Inherits GenericController

    End Class

    <DAType()> _
    Friend Class CustomController
        <DANode(), DAItem()> _
        Public Property Input1 As Double

        <DANode(), DAItem()> _
        Public Property Input2 As Double

        <DANode(), DAItem()> _
        Public Property Input3 As Double

        <DANode(), DAItem(Operations:=DAItemMappingOperations.ReadAndSubscribe)> _
        Public Property ControlOut As Double

        <DANode(), DAItem()> _
        Public Property Description As String
    End Class

    <DAType()> _
    Friend Class FlowTransmitter
        Inherits GenericSensor

    End Class

    <DAType()> _
    Friend Class Valve
        Inherits GenericActuator

    End Class

    <DAType()> _
    Friend Class LevelIndicator
        Inherits GenericSensor

    End Class

    <DAType()> _
    Friend Class GenericController
        <DANode(), DAItem(Operations:=DAItemMappingOperations.ReadAndSubscribe)> _
        Public Property Measurement As Double

        <DANode(), DAItem()> _
        Public Property SetPoint As Double

        <DANode(), DAItem(Operations:=DAItemMappingOperations.ReadAndSubscribe)> _
        Public Property ControlOut As Double
    End Class

    <DAType()> _
    Friend Class GenericSensor
        ' Meta-members are filled in by information collected during mapping, and allow access to it later from your code.
        ' Alternatively, you can derive your class from DAMappedNode, which will bring in many meta-members automatically.
        <MetaMember("NodeDescriptor")> _
        Public Property NodeDescriptor As DANodeDescriptor

        <DANode(), DAItem(Operations:=DAItemMappingOperations.ReadAndSubscribe)> _
        Public Property Output() As Double ' no OPC writing
            Get
                Return _output
            End Get
            Set(ByVal value As Double)
                _output = value
                Console.WriteLine("Sensor ""{0}"" output is now {1}.", NodeDescriptor, value)
            End Set
        End Property

        Private _output As Double
    End Class

    <DAType()> _
    Friend Class GenericActuator
        <DANode(), DAItem()> _
        Public Property Input As Double
    End Class
End Namespace

 

See Also

Conceptual